#include <retro.h>
#include "retro.creator.h"

#define RETRO_EDITOR_MINZOOM  25
#define RETRO_EDITOR_MAXZOOM  200

#define LAYER_FORMAT          _T("L%d")

using namespace Retro::Creator::Editor;
using namespace SPLib::Geom;

#pragma region Menus

Menu MapEditor::menuLayers;
Menu MapEditor::menuDisplay;
Menu MapEditor::menuTools;

#pragma endregion

MapEditor::MapEditor(Data::ResourceType iType)
  : EditorBase(iType)
{
  mDisplay    = MapDisplayShadow;
  mSelectMode = SelectModeNone;
  mMapLayers  = 0;
  mMapWidth   = 0;
  mMapHeight  = 0;
  mLayer      = -1;
  mTool       = MapToolNone;
  mDevice     = NULL;
  mForeColor  = 0xFF000000;
  mShowGrid   = FALSE;
  mGridSize   = 32;
  mZoom       = 100;
}

BOOL    MapEditor::init()
{
  // register window class
  registerClass(RETRO_MAPEDITORCLASS, CS_CLASSDC);

  // create display menu
  {
    menuDisplay.createPopup();
    menuDisplay.append(MapDisplayShadow,  _T("Shadow"));
    menuDisplay.append(MapDisplayCurrent, _T("Current"));
    menuDisplay.append(MapDisplayAll,     _T("All"));
  }

  // create menus
  menuLayers.createPopup();
  menuTools.createPopup();

  return TRUE;
}

VOID    MapEditor::uninit()
{
  menuLayers.destroy();
  menuDisplay.destroy();
  menuTools.destroy();
}

VOID    MapEditor::initToolbar()
{
  TCHAR wText[RETRO_MAXTEXT];

  Menu_GetItemText(getMenuDisplay(), mDisplay, wText);
  getToolBar()->setItemText(wText, Editor::MapItemDisplay);

  _stprintf(wText, LAYER_FORMAT, getLayer() + 1);
  getToolBar()->setItemText(wText, Editor::MapItemLayer);

  EditorBase::initToolbar();
}

VOID    MapEditor::adjustMousePos(Geom::Point &pt)
{
  Point ptMax = { getBufferWidth(), getBufferHeight() };

  pt.x = (LONG)(pt.x * 100 / getZoom() + mView.x);
  pt.y = (LONG)(pt.y * 100 / getZoom() + mView.y);

  // TODO: crop scaled selection (SHIFT hold)
  if (pt.x < 0) pt.x = 0;
  if (pt.y < 0) pt.y = 0;
  if (pt.x > ptMax.x) pt.x = ptMax.x;
  if (pt.y > ptMax.y) pt.y = ptMax.y;
}

VOID    MapEditor::updateLayers()
{
  menuLayers.clear();

  for (int i = 1; i <= mMapLayers; ++i)
  {
    TCHAR szText[RETRO_MAXTEXT];

    _stprintf(szText, _T("Layer %d"), i);
    menuLayers.append(i, szText);
  }

  if (mLayer >= mMapLayers)
    setLayer(mMapLayers - 1);
}

// EVENTS ---------------------------------------------------------------------

VOID    MapEditor::onCreate(Event::WndCreateEvent& iEvent)
{
  // setup display device
  mDevice = Device::Display::CreateDevice(Device::LibraryD3D);
  mDevice->setWindow(getHandle());

  addStyle(WS_TABSTOP);
}

VOID    MapEditor::onDestroy()
{
  // release map resources
  setResource(NULL);

  // release rendering resources
  if (mDevice)
  {
    mDevice->uninitialize();
    SafeDelete(mDevice);
  }
}

VOID    MapEditor::drawBackground()
{
  if (getDevice()->isWireframe())
  {
    getDevice()->clear(0xFF000000);
  }
  else
  {
    LONG wCellSize = mGridSize / 2;

    Geom::Size  wSize = getSize() * 100 / getZoom();
    Geom::Size  wCellCount = {
      (LONG)ceil((DOUBLE)wSize.cx / wCellSize),
      (LONG)ceil((DOUBLE)wSize.cy / wCellSize)
    };

    getDevice()->beginBatch(NULL);
    getDevice()->addBatch(
      Geom::Bounds::get(0, 0, wSize.cx, wSize.cy),
      0xFFFFFFFF);

    for (INT x = 0; x < wCellCount.cx; ++x)
    for (INT y = 0; y < wCellCount.cy; ++y)
    {
      if ((x + y) % 2 == 0)
      {
        getDevice()->addBatch(
          Geom::Bounds::get(x * wCellSize, y * wCellSize, wCellSize, wCellSize),
          0xFFCCCCCC);
      }
    }
  }
}

VOID    MapEditor::drawForeground()
{
  if (getShowGrid())
    drawGrid(mGridSize);
}

BOOL    MapEditor::render(Device::Display::DisplayDevice* iDevice)
{
  if (!iDevice)
    return FALSE;

  FCHECK(iDevice->beginScene());

  iDevice->resetTransform();

  drawBackground();

  // render scene
  if (mDisplay != MapDisplayNone)
  {
    for (int i = 0; i < mMapLayers; ++i)
    {
      if (mDisplay == MapDisplayCurrent && i != mLayer)
        continue;

      // if higher than current layer, set alpha at 50%
      // if lower than current layer, set lightness at 75%
      if (mDisplay == MapDisplayShadow)
      {
        if (i > mLayer)
        {
          iDevice->setAlpha(0x7F);
        }
        else if (i < mLayer)
        {
          iDevice->setLightness(0xBF);
        }
        else
        {
          iDevice->setAlpha(0xFF);
          iDevice->setLightness(0xFF);
        }
      }
      else
      {
        iDevice->setAlpha(0xFF);
        iDevice->setLightness(0xFF);
      }

      drawLayer(i);

      iDevice->end();
    }
  }

  drawForeground();

  FCHECK(iDevice->endScene());

  return TRUE;
}

VOID    MapEditor::onPaint()
{
  render(mDevice);
}

VOID    MapEditor::onEraseBackground(Event::EraseBackgroundEvent& iEvent)
{
  *iEvent.handled = TRUE;
}

VOID    MapEditor::onMouseWheel(Event::MouseWheelEvent& iEvent)
{
  if (!getMap()) return;

  if (GetKeyState(VK_CONTROL) >> 1)
  {
    setZoom(getZoom() + (iEvent.wheelDelta / WHEEL_DELTA) * 5);
  }
  else
  {
    INT wScroll = INT(
      iEvent.wheelDelta / WHEEL_DELTA *
      getLineSize() * getZoom() / 100);

    if (GetKeyState(VK_SHIFT) >> 1 ||
      iEvent.wheelOrientation == Event::Horizontal)
    {
      Frame::get()->getSurface()->setScrollX(
        Frame::get()->getSurface()->getScroll().x - wScroll);
    }
    else
    {
      Frame::get()->getSurface()->setScrollY(
        Frame::get()->getSurface()->getScroll().y - wScroll);
    }
  }
}

VOID    MapEditor::onKeyDown(Event::KeyEvent& iEvent)
{
  if (!getMap()) return;

  if (GetKeyState(VK_CONTROL) >> 1) switch (iEvent.vkCode)
  {
  case '0':
  case '1':
    setZoom(100);
    break;
  case '2':
    setZoom(125);
    break;
  case '3':
    setZoom(150);
    break;
  case '4':
    setZoom(175);
    break;
  case '5':
    setZoom(200);
    break;
  case '7':
    setZoom(75);
    break;
  case '8':
    setZoom(50);
    break;
  case '9':
    setZoom(25);
    break;
  case 'A':
    mSelection = Bounds::get(0, 0, getBufferWidth(), getBufferHeight());
    invalidate(FALSE);
    break;
  case VK_PRIOR:
    Frame::get()->getSurface()->setScrollX(
      Frame::get()->getSurface()->getScroll().x -
      (INT)(mView.cx * getZoom() / 100));
    break;
  case VK_NEXT:
    Frame::get()->getSurface()->setScrollX(
      Frame::get()->getSurface()->getScroll().x +
      (INT)(mView.cx * getZoom() / 100));
    break;
  case VK_HOME:
    Frame::get()->getSurface()->setScrollX(0);
    break;
  case VK_END:
    Frame::get()->getSurface()->setScrollX(
      Frame::get()->getSurface()->getScrollMax().x);
    break;
  }
  else switch (iEvent.vkCode)
  {
  case VK_ADD:
    setZoom(getZoom() + 5);
    break;
  case VK_SUBTRACT:
    setZoom(getZoom() - 5);
    break;
  case VK_PRIOR:
    Frame::get()->getSurface()->setScrollY(
      Frame::get()->getSurface()->getScroll().y -
      (INT)(mView.cy * getZoom() / 100));
    break;
  case VK_NEXT:
    Frame::get()->getSurface()->setScrollY(
      Frame::get()->getSurface()->getScroll().y +
      (INT)(mView.cy * getZoom() / 100));
    break;
  case VK_HOME:
    Frame::get()->getSurface()->setScrollY(0);
    break;
  case VK_END:
    Frame::get()->getSurface()->setScrollY(
      Frame::get()->getSurface()->getScrollMax().y);
    break;
  }
}

VOID    MapEditor::onToolbar(ToolBarNotify* iData)
{
  switch (iData->itemID)
  {
  case MapItemDisplay:
    setDisplay((Editor::MapDisplay)iData->value);
    break;
  case MapItemLayer:
    setLayer(iData->value - 1);
    break;
  case MapItemTool:
    setTool((Editor::MapTool)iData->value);
    break;
  case MapItemToggleGrid:
    setShowGrid(!getShowGrid());
    break;
  default:
    Frame::get()->doAction((FrameAction)(iData->value ?
      iData->value : iData->itemID));
    break;
  }
}

VOID    MapEditor::drawGrid(INT size)
{
  Geom::Rect gridView = {
    mView.x / size,
    mView.y / size,
    (LONG)ceilf(((REAL)mView.x + mView.cx) / size),
    (LONG)ceilf(((REAL)mView.y + mView.cy) / size)
  };

  gridView.right  = min(mMapWidth, gridView.right);
  gridView.bottom = min(mMapHeight, gridView.bottom);

  mDevice->beginLine();

  for (int i = gridView.left; i <= gridView.right; ++i)
  {
    int x = (int)(i * size - mView.x);

    mDevice->drawLine(x,      0,        x,          mView.cy,   mForeColor);
    mDevice->drawLine(x - 1,  0,        x - 1,      mView.cy,   mForeColor);
  }

  for (int i = gridView.top; i <= gridView.bottom; ++i)
  {
    int y = (int)(i * size - mView.y);

    mDevice->drawLine(0,      y,        mView.cx,   y,          mForeColor);
    mDevice->drawLine(0,      y - 1,    mView.cx,   y - 1,      mForeColor);
  }

  mDevice->end();
}

// GETTERS & SETTERS ----------------------------------------------------------

VOID    MapEditor::setResource(Data::Resource* value)
{
  menuTools.clear();

  if (getMap())
  {
    flush(getMap());
    clearCopy();
    clearUndo();
    freeMapBuffer();
    onUninitializeDisplay();
  }

  EditorBase::setResource(value);

  if (value)
  {
    mMapWidth    = getMap()->getWidth();
    mMapHeight   = getMap()->getHeight();
    mMapLayers   = getMap()->getLayers();

    createMapBuffer();

    setZoom(100);
    setLayer(0);
    setDisplay(MapDisplayShadow);

    mView.setEmpty();
    mSelection.setEmpty();

    updateLayers();

    onPrepareDisplay();
    mDevice->initialize();
    onInitializeDisplay();

    show();
  }
  else
  {
    mLayer = -1;

    mDevice->uninitialize();
    hide();
  }
}

INT     MapEditor::getZoom()
{
  return mZoom;
}

VOID    MapEditor::setZoom(INT value)
{
  if (value < RETRO_EDITOR_MINZOOM) value = RETRO_EDITOR_MINZOOM;
  if (value > RETRO_EDITOR_MAXZOOM) value = RETRO_EDITOR_MAXZOOM;

  EditorBase::setZoom(mZoom = value);

  sendNotify(EditorNotifyZoom);
  invalidate(FALSE);
}

VOID    MapEditor::setLayer(INT value)
{
  if (value < 0) value = 0;
  if (value >= mMapLayers) value = mMapLayers - 1;

  if (mLayer != value)
  {
    TCHAR wText[RETRO_MAXTEXT];

    mLayer = value;

    if (getToolBar())
    {
      _stprintf(wText, LAYER_FORMAT, value + 1);
      getToolBar()->setItemText(wText, Editor::MapItemLayer);
    }

    sendNotify(MapNotifyLayer);
    invalidate(FALSE);
  }
}

VOID    MapEditor::setTool(MapTool value)
{
  TCHAR wText[RETRO_MAXTEXT];

  mTool = value;

  Menu_GetItemText(getToolMenu(), getTool(), wText);
  if (getToolBar())
    getToolBar()->setItemText(wText, Editor::MapItemTool);

  sendNotify(MapNotifyTool);
  invalidate(FALSE);
}

VOID    MapEditor::setDisplay(MapDisplay iValue)
{
  TCHAR wText[RETRO_MAXTEXT];

  mDisplay = iValue;

  if (getToolBar())
  {
    Menu_GetItemText(getMenuDisplay(), mDisplay, wText);
    getToolBar()->setItemText(wText, Editor::MapItemDisplay);
  }

  sendNotify(MapNotifyDisplay);
  invalidate(FALSE);
}

VOID    MapEditor::setShowGrid(BOOL value)
{
  mShowGrid = value;

  if (getToolBar())
    getToolBar()->setItemChecked(Editor::MapItemToggleGrid, value);

  sendNotify(MapNotifyGrid);
  invalidate(FALSE);
}

INT     MapEditor::getLineSize()
{
  return mGridSize;
}
